﻿using System.Collections.Generic;
using UnityEngine;

namespace CWM.Skinn
{
    public static partial class SkinnEx
    {
        public static void LightMapAtlas(Renderer context, int atlasSize, Material preset = null)
        {
            var sharedMaterials = context.GetSharedMaterialsFromSubMeshes();
            context.sharedMaterials = sharedMaterials;
            var textures = LMAtlasUtility.RenderAllTextures(context, atlasSize);

            var tempMesh = context.GetSharedMesh().Clone() as Mesh;
            tempMesh.subMeshCount = 1;
            tempMesh.SetTriangles(context.GetSharedMesh().triangles, 0);
            tempMesh.uv = context.GetSharedMesh().uv2;

            var maxMaterialIndex = -1;
            var maxPropertyCount = -1;
            for (int i = 0; i < sharedMaterials.Length; i++)
            {
                var propertyCount = 0;
                if (!sharedMaterials[i]) continue;
                foreach (var item in textures) if (sharedMaterials[i].HasProperty(item.Key)) propertyCount++;
                if (propertyCount > maxPropertyCount) maxMaterialIndex = i;
            }

            if (maxMaterialIndex > -1)
            {
                Material combinedMat = null;
                if (preset)
                {
                    combinedMat = new Material(preset) { name = context.name };
                }
                else
                {
                    combinedMat = new Material(sharedMaterials[maxMaterialIndex].shader) { name = context.name };
                }

                foreach (var item in textures)
                {
                    var texture = item.Value;
                    if (!texture) continue;
                    if (LMAtlasUtility.GetColorProperty(combinedMat) == item.Key)
                    {
                        if (!textures.ContainsKey("_MainTex") && combinedMat.HasProperty("_MainTex")) combinedMat.SetTexture("_MainTex", texture);
                    }
                    else if (combinedMat.HasProperty(item.Key)) combinedMat.SetTexture(item.Key, texture);

                }
                context.sharedMaterials = new Material[] { combinedMat };
            }
            else context.sharedMaterials = new Material[1];
            context.SetSharedMesh(tempMesh);
        }

        public static class LMAtlasUtility
        {

            public static readonly string[] KnowTextureProperties = new string[]
               {
                "_MainTex",
                "_NormalMap",
                "_BumpMap",
                "_MetallicGlossMap",
                "_ParallaxMap",
                "_OcclusionMap",
                "_EmissionMap",
                "_DetailMask",
                "_DetailAlbedoMap",
                "_DetailNormalMap",
                "_BaseColorMap",
                "_NormalMapOS",
                "_BentNormalMap",
                "_BentNormalMapOS",
                "_HeightMap",
                "_DetailMap",
                "_TangentMap",
                "_TangentMapOS",
                "_AnisotropyMap",
                "_SubsurfaceMaskMap",
                "_ThicknessMap",
                "_IridescenceThicknessMap",
                "_IridescenceMaskMap",
                "_CoatMaskMap",
                "_SpecularColorMap",
                "_EmissiveColorMap",
                "_TransmittanceColorMap",
                "_DistortionVectorMap",
               };


            public static string GetColorProperty(Material material)
            {
                var propertyName = "";
                if (!material || !material.shader) return propertyName;
                if (material.HasProperty("_Color")) return "_Color";
                if (material.HasProperty("_MainColor")) return "_MainColor";
                return propertyName;
            }

            public static string GetMainTextureProperty(Material material)
            {
                var propertyName = "";
                if (!material || !material.shader) return propertyName;
                if (material.HasProperty("_MainTex")) return "_MainTex";
                if (material.HasProperty("_BaseColorMap")) return "_BaseColorMap";
                if (material.HasProperty("_EmissiveColorMap")) return "_EmissiveColorMap";
                return propertyName;
            }

            public static string GetEmissionColorProperty(Material material)
            {
                var propertyName = "";
                if (!material || !material.shader) return propertyName;
                if (material.HasProperty("_EmissionColor")) return "_EmissionColor";
                return propertyName;
            }

            public static string GetEmissionTextureProperty(Material material)
            {
                var propertyName = "";
                if (!material || !material.shader) return propertyName;
                if (material.HasProperty("_EmissionMap")) return "_EmissionMap";
                if (material.HasProperty("_EmissiveColorMap")) return "_EmissiveColorMap";
                return propertyName;
            }

            public static Dictionary<string, RenderTexture> RenderAllTextures(Renderer context, int atlasSize)
            {
                //const bool padTextures = true;
                var textures = new Dictionary<string, RenderTexture>();
                if (IsNullOrNotVailid(context)) return textures;
                var shader = SkinnInternalAsset.Asset.lightMapAtlas;
                var renderMaterial = new Material(shader) { name = "uv", hideFlags = HideFlags.HideAndDontSave };
                var mesh = context.GetSharedMesh();
                var materials = context.GetSharedMaterialsFromSubMeshes();
                var matDictionary = new Dictionary<string, List<int>>();
                for (int i = 0; i < materials.Length; i++)
                {
                    var material = materials[i]; if (!material) continue;
                    string[] textureIDs = GetPropertiesWithTextures(material);
                    for (int ii = 0; ii < textureIDs.Length; ii++)
                    {
                        var property = textureIDs[ii];
                        if (matDictionary.ContainsKey(property))
                        {
                            List<int> mats = new List<int>();
                            matDictionary.TryGetValue(property, out mats);
                            matDictionary.Remove(property);
                            mats.Add(i);
                            matDictionary.Add(property, mats);
                        }
                        else matDictionary.Add(property, new List<int>(1) { i });
                    }
                    if (textureIDs.Length > 0) continue;
                    var colorProp = GetColorProperty(material);
                    if (string.IsNullOrEmpty(colorProp)) continue;

                    if (matDictionary.ContainsKey(colorProp))
                    {
                        List<int> mats = new List<int>();
                        matDictionary.TryGetValue(colorProp, out mats);
                        matDictionary.Remove(colorProp);
                        mats.Add(i);
                        matDictionary.Add(colorProp, mats);
                    }
                    else matDictionary.Add(colorProp, new List<int>(1) { i });

                 
                }

                foreach (KeyValuePair<string, List<int>> mat in matDictionary)
                {
                    var materialIndex = mat.Value;
                    float isNormalMap = mat.Key.Contains("Bump") || mat.Key.Contains("Normal") ? 1 : 0;
                    renderMaterial.SetFloat("_ConvertBumpMap", isNormalMap);
                    var atlasRTexture = new RenderTexture(atlasSize, atlasSize, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB) { };
                    atlasRTexture.name = context.name + "_" + mat.Key + "_Atlas.png";
                    var tempRTexture = new RenderTexture(atlasSize, atlasSize, 0) { enableRandomWrite = true };
                    tempRTexture.Create();

                    var active = RenderTexture.active;
                    RenderTexture.active = atlasRTexture;
                    {
                        for (int i = 0; i < materialIndex.Count; i++)
                        {
                            var material = materials[materialIndex[i]];
                            
                            if (GetColorProperty(material) == mat.Key)
                            {
                                renderMaterial.SetFloat("_UseColor", 1);
                                Color propColor = material.GetColor(mat.Key);
                                propColor.a = Mathf.Clamp(propColor.a, 1e-8f, 1);
                                renderMaterial.SetColor("_Color", propColor);
                                renderMaterial.SetTexture("_MainTex", null);
                            }
                            else
                            {
                                var offest = material.GetTextureOffset(mat.Key);
                                var scale = material.GetTextureScale(mat.Key);

                                renderMaterial.SetTextureOffset("_MainTex", offest);
                                renderMaterial.SetTextureOffset("_MainTex", scale);

                                var mainTexProp = GetMainTextureProperty(material);
                                var colorProp = GetColorProperty(material);
                                var emissionTexProp = GetEmissionTextureProperty(material);
                                var emissionColorProp = GetEmissionColorProperty(material);

                                if (mat.Key == mainTexProp && material.HasProperty(colorProp))
                                {
                                    renderMaterial.SetFloat("_UseColor", 1);
                                    renderMaterial.SetColor("_Color", material.GetColor(colorProp));
                                    renderMaterial.SetTexture("_MainTex", material.GetTexture(mat.Key));
                                }
                                else if (mat.Key == emissionTexProp && material.HasProperty(emissionColorProp))
                                {
                                    renderMaterial.SetFloat("_UseColor", 1);
                                    var emissionColor = material.GetColor(emissionColorProp);
                                    emissionColor.a = 1;
                                    renderMaterial.SetColor("_Color", emissionColor);
                                    renderMaterial.SetTexture("_MainTex", material.GetTexture(mat.Key));
                                }
                                else
                                {
                                    renderMaterial.SetFloat("_UseColor", 0);
                                    renderMaterial.SetColor("_Color", Color.white);
                                    renderMaterial.SetTexture("_MainTex", material.GetTexture(mat.Key));
                                }

                                
                            }

                            renderMaterial.SetPass(0);

                            Graphics.DrawMeshNow(mesh, Matrix4x4.identity, materialIndex[i]);
                        }
                        //if (padTextures) for (int i = 0; i < 3; i++) PadTexture(atlasRTexture, tempRTexture, 1.001f + (i * 0.0001f));
                    }

                    RenderTexture.active = active;
                    Release(tempRTexture);
                    textures.Add(mat.Key, atlasRTexture);
                }
                Release(renderMaterial);
                return textures;
            }

            private static void PadTexture(RenderTexture source, RenderTexture temp, float size = 1.1f)
            {
                ComputeShader shader = SkinnInternalAsset.Asset.texturePadding;
                var kernal = shader.FindKernel("AlphaPadding");
                shader.SetTexture(kernal, "Source", source);
                shader.SetTexture(kernal, "Destination", temp);
                shader.SetFloat("Size", size);
                shader.Dispatch(kernal, (temp.width + 7) / 8, (temp.height + 7) / 8, 1);
                Graphics.Blit(temp, source);
            }

            private static void PadTextureEdgeBlending(RenderTexture source, RenderTexture temp, float size = 1.1f)
            {
                ComputeShader shader = SkinnInternalAsset.Asset.texturePadding;
                var kernal = shader.FindKernel("EdgeBlending");
                shader.SetTexture(kernal, "Source", source);
                shader.SetTexture(kernal, "Destination", temp);
                shader.SetFloat("Size", size);
                shader.Dispatch(kernal, (temp.width + 7) / 8, (temp.height + 7) / 8, 1);
                Graphics.Blit(temp, source);
            }

            public static string[] GetPropertiesWithTextures(Material material)
            {
                if (material == null || material.shader == null) return new string[0];
                List<string> propertyNames = new List<string>();
                var shader = material.shader;

                //{
                //    var propertyCount = UnityEditor.ShaderUtil.GetPropertyCount(shader);
                //    for (int i = 0; i < propertyCount; i++)
                //    {
                //        if (UnityEditor.ShaderUtil.GetPropertyType(shader, i) != UnityEditor.ShaderUtil.ShaderPropertyType.TexEnv) continue;
                //        var propertyName = UnityEditor.ShaderUtil.GetPropertyName(shader, i);
                //        Debug.Log(propertyName);
                //    }
                //}

                int textureCount = 0;
                for (int i = 0; i < KnowTextureProperties.Length; i++)
                {
                    if (!material.HasProperty(KnowTextureProperties[i])) continue;
                    var texture = material.GetTexture(KnowTextureProperties[i]);
                    if (!texture) continue;
                    propertyNames.Add(KnowTextureProperties[i]);
                    textureCount++;
                }
                return propertyNames.ToArray();
            }



        }
    }
}
